﻿using System;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Globalization;

namespace Client.Behaviors
{
    public class TextBoxMaskBehavior
    {
        #region MinimumValue Property

        public static double GetMinimumValue(DependencyObject obj)
        {
            return (double)obj.GetValue(MinimumValueProperty);
        }

        public static void SetMinimumValue(DependencyObject obj, double value)
        {
            obj.SetValue(MinimumValueProperty, value);
        }

        public static readonly DependencyProperty MinimumValueProperty =
            DependencyProperty.RegisterAttached(
                "MinimumValue",
                typeof(double),
                typeof(TextBoxMaskBehavior),
                new FrameworkPropertyMetadata(double.NaN, MinimumValueChangedCallback)
                );

        private static void MinimumValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var @this = (d as TextBox);
            ValidateTextBox(@this);
        }
        #endregion

        #region MaximumValue Property

        public static double GetMaximumValue(DependencyObject obj)
        {
            return (double)obj.GetValue(MaximumValueProperty);
        }

        public static void SetMaximumValue(DependencyObject obj, double value)
        {
            obj.SetValue(MaximumValueProperty, value);
        }

        public static readonly DependencyProperty MaximumValueProperty =
            DependencyProperty.RegisterAttached(
                "MaximumValue",
                typeof(double),
                typeof(TextBoxMaskBehavior),
                new FrameworkPropertyMetadata(double.NaN, MaximumValueChangedCallback)
                );

        private static void MaximumValueChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var @this = (d as TextBox);
            ValidateTextBox(@this);
        }
        #endregion

        #region Mask Property

        public static MaskType GetMask(DependencyObject obj)
        {
            return (MaskType)obj.GetValue(MaskProperty);
        }

        public static void SetMask(DependencyObject obj, MaskType value)
        {
            obj.SetValue(MaskProperty, value);
        }

        public static readonly DependencyProperty MaskProperty =
            DependencyProperty.RegisterAttached(
                "Mask",
                typeof(MaskType),
                typeof(TextBoxMaskBehavior),
                new FrameworkPropertyMetadata(MaskChangedCallback)
                );

        private static void MaskChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (e.OldValue is TextBox)
            {
                (e.OldValue as TextBox).PreviewTextInput -= TextBoxPreviewTextInput;
                DataObject.RemovePastingHandler((e.OldValue as TextBox), TextBoxPastingEventHandler);
            }

            var @this = (d as TextBox);
            if (@this == null)
                return;

            if ((MaskType)e.NewValue != MaskType.Any)
            {
                @this.PreviewTextInput += TextBoxPreviewTextInput;
                DataObject.AddPastingHandler(@this, TextBoxPastingEventHandler);
            }

            ValidateTextBox(@this);
        }

        #endregion

        #region Private Static Methods

        private static void ValidateTextBox(TextBox @this)
        {
            if (GetMask(@this) != MaskType.Any)
            {
                @this.Text = ValidateValue(GetMask(@this), @this.Text, GetMinimumValue(@this), GetMaximumValue(@this));
            }
        }

        private static void TextBoxPastingEventHandler(object sender, DataObjectPastingEventArgs e)
        {
            var @this = (TextBox) sender;
            var clipboard = e.DataObject.GetData(typeof(string)) as string;
            clipboard = ValidateValue(GetMask(@this), clipboard, GetMinimumValue(@this), GetMaximumValue(@this));
            if (!string.IsNullOrEmpty(clipboard))
            {
                @this.Text = clipboard;
            }
            e.CancelCommand();
            e.Handled = true;
        }

        private static void TextBoxPreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e)
        {
            var @this = (TextBox) sender;
            var isValid = IsSymbolValid(GetMask(@this), e.Text);
            e.Handled = !isValid;
            if (!isValid) return;
            var caret = @this.CaretIndex;
            var text = @this.Text;
            var textInserted = false;
            var selectionLength = 0;

            if (@this.SelectionLength > 0)
            {
                text = text.Substring(0, @this.SelectionStart) +
                       text.Substring(@this.SelectionStart + @this.SelectionLength);
                caret = @this.SelectionStart;
            }

            if (e.Text == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
            {
                while (true)
                {
                    var ind = text.IndexOf(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator);
                    if (ind == -1)
                        break;

                    text = text.Substring(0, ind) + text.Substring(ind + 1);
                    if (caret > ind)
                        caret--;
                }

                if (caret == 0)
                {
                    text = "0" + text;
                    caret++;
                }
                else
                {
                    if (caret == 1 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign)
                    {
                        text =  NumberFormatInfo.CurrentInfo.NegativeSign + "0" + text.Substring(1);
                        caret++;
                    }
                }

                if (caret == text.Length)
                {
                    selectionLength = 1;
                    textInserted = true;
                    text = text + NumberFormatInfo.CurrentInfo.NumberDecimalSeparator + "0";
                    caret++;
                }
            }
            else if (e.Text == NumberFormatInfo.CurrentInfo.NegativeSign)
            {
                textInserted = true;
                if (@this.Text.Contains(NumberFormatInfo.CurrentInfo.NegativeSign))
                {
                    text = text.Replace(NumberFormatInfo.CurrentInfo.NegativeSign, string.Empty);
                    if (caret != 0)
                        caret--;
                }
                else
                {
                    text = NumberFormatInfo.CurrentInfo.NegativeSign + @this.Text;
                    caret++;
                }
            }

            if (!textInserted)
            {
                text = text.Substring(0, caret) + e.Text +
                       ((caret < @this.Text.Length) ? text.Substring(caret) : string.Empty);

                caret++;
            }

            try
            {
                var val = Convert.ToDouble(text);
                var newVal = ValidateLimits(GetMinimumValue(@this), GetMaximumValue(@this), val);
                if (val != newVal)
                {
                    text = newVal.ToString();
                }
                else if (val == 0)
                {
                    if (!text.Contains(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator))
                        text = "0";
                }
            }
            catch
            {
                text = "0";
            }

            while (text.Length > 1 && text[0] == '0' && string.Empty + text[1] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
            {
                text = text.Substring(1);
                if (caret > 0) caret--;
            }

            while (text.Length > 2 && string.Empty + text[0] == NumberFormatInfo.CurrentInfo.NegativeSign && text[1] == '0' && string.Empty + text[2] != NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
            {
                text = NumberFormatInfo.CurrentInfo.NegativeSign + text.Substring(2);
                if (caret > 1) caret--;
            }

            if (caret > text.Length) caret = text.Length;

            @this.Text = text;
            @this.CaretIndex = caret;
            @this.SelectionStart = caret;
            @this.SelectionLength = selectionLength;
            e.Handled = true;
        }

        private static string ValidateValue(MaskType mask, string value, double min, double max)
        {
            if (string.IsNullOrEmpty(value)) return string.Empty;

            value = value.Trim();
            switch (mask)
            {
                case MaskType.Integer:
                    try
                    {
                        Convert.ToInt64(value);
                        return value;
                    }
                    catch
                    {
                        return string.Empty;
                    }

                case MaskType.Decimal:
                    try
                    {
                        Convert.ToDouble(value);
                        return value;
                    }
                    catch
                    {
                        return string.Empty;
                    }
            }

            return value;
        }

        private static double ValidateLimits(double min, double max, double value)
        {
            if (!min.Equals(double.NaN))
            {
                if (value < min)
                    return min;
            }

            if (!max.Equals(double.NaN))
            {
                if (value > max)
                    return max;
            }

            return value;
        }

        private static bool IsSymbolValid(MaskType mask, string str)
        {
            switch (mask)
            {
                case MaskType.Any:
                    return true;

                case MaskType.Integer:
                    if (str == NumberFormatInfo.CurrentInfo.NegativeSign)
                        return true;
                    break;

                case MaskType.Decimal:
                    if (str == NumberFormatInfo.CurrentInfo.NumberDecimalSeparator ||
                        str == NumberFormatInfo.CurrentInfo.NegativeSign)
                        return true;
                    break;
            }

            if (mask.Equals(MaskType.Integer) || mask.Equals(MaskType.Decimal))
            {
                return str.All(Char.IsDigit);
            }

            return false;
        }

        #endregion
    }
}